
package com.huaweicloud.saaslanding.install.applicationservice.impl;

import com.huaweicloud.saaslanding.basic.database.mybatis.conditions.query.QueryWrap;
import com.huaweicloud.saaslanding.basic.enumeration.ComponentLabelEnum;
import com.huaweicloud.saaslanding.basic.utils.ResourceNamingUtil;
import com.huaweicloud.saaslanding.dao.common.entity.app.AppComponent;
import com.huaweicloud.saaslanding.dao.common.entity.cloudservice.CceInfo;
import com.huaweicloud.saaslanding.dao.common.entity.deployedresource.CceDeploymentResource;
import com.huaweicloud.saaslanding.dao.common.entity.deployedresource.LogResourceOp;
import com.huaweicloud.saaslanding.dao.common.entity.deployedresource.LogResourceSys;
import com.huaweicloud.saaslanding.dao.common.repository.app.AppTemplateBaseMapper;
import com.huaweicloud.saaslanding.dao.common.repository.cloudservice.CceInfoBaseMapper;
import com.huaweicloud.saaslanding.dao.common.repository.deployedresource.CceDeploymentResourceBaseMapper;
import com.huaweicloud.saaslanding.dao.common.repository.deployedresource.LogResourceOpBaseMapper;
import com.huaweicloud.saaslanding.dao.common.repository.deployedresource.LogResourceSysBaseMapper;
import com.huaweicloud.saaslanding.huawei.iam.api.IamApi;
import com.huaweicloud.saaslanding.huawei.lts.LtsApi;
import com.huaweicloud.saaslanding.huawei.lts.constant.LtsConstant;
import com.huaweicloud.saaslanding.huawei.lts.model.AomMappingRuleResp;
import com.huaweicloud.saaslanding.huawei.lts.model.LogFile;
import com.huaweicloud.saaslanding.huawei.lts.model.LogStreamInfo;
import com.huaweicloud.saaslanding.huawei.lts.model.RuleInfo;
import com.huaweicloud.saaslanding.huawei.lts.request.CreateLogGroupRequestBody;
import com.huaweicloud.saaslanding.huawei.lts.request.CreateLogStreamRequestBody;
import com.huaweicloud.saaslanding.huawei.lts.request.CreateMappingRuleRequestBody;
import com.huaweicloud.saaslanding.install.applicationservice.LtsService;
import com.huaweicloud.saaslanding.install.properties.Iam;
import com.huaweicloud.saaslanding.install.repository.ComponentInfoMapper;
import com.huaweicloud.saaslanding.install.repository.RdsRepository;

import cn.hutool.core.collection.ListUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 功能描述
 *
 * @since 2022-08-08
 */
@Slf4j
@Component
@ConditionalOnProperty(prefix = "saaslanding-install.lts", name = "enabled")
public class LtsServiceImpl implements LtsService {
    private static final String SYS_LOG_TABLE_NAME = "_log_resource_sys";

    private static final String SYS_LOG_SUFFIX = "-sys";

    private static final String OP_LOG_SUFFIX = "-op";

    private static final Integer LOG_MAX_TTL = 30;

    @Resource
    private CceDeploymentResourceBaseMapper cceDeploymentResourceBaseMapper;

    @Resource
    private LogResourceSysBaseMapper logResourceSysBaseMapper;

    @Resource
    private LogResourceOpBaseMapper logResourceOpBaseMapper;

    @Resource
    private AppTemplateBaseMapper appTemplateBaseMapper;

    @Resource
    private ComponentInfoMapper componentInfoMapper;

    @Resource
    private CceInfoBaseMapper cceInfoBaseMapper;

    @Resource
    private RdsRepository rdsRepository;

    @Resource
    private LtsApi ltsApi;

    @Resource
    private Iam iam;

    @Value("${saaslanding-install.lts.endpoint}")
    private String ltsEndpoint;

    @Value("${saaslanding-install.database.connection-info.db-name}")
    private String database;

    @Override
    public void tryInitOpLogResource() {
        log.info("========== start: 创建操作日志资源 ==========");
        rdsRepository.createLogResourceTable(database, ResourceNamingUtil.getPrefixCode());

        // IAM API:构造带有 IAM Token 的请求头
        Map<String, String> headers = IamApi.getIamHeader(iam.getEndpoint(), iam.getIamDomain(), iam.getIamUser(),
            iam.getIamPassword(), iam.getProjectId());

        String logName = database.concat(OP_LOG_SUFFIX);
        // LTS API:创建日志组
        CreateLogGroupRequestBody createLogGroupRequestBody =
            new CreateLogGroupRequestBody().setLogGroupName(logName).setTtlInDays(LOG_MAX_TTL);
        String logGroupId = ltsApi.createLogGroup(ltsEndpoint, headers, iam.getProjectId(), createLogGroupRequestBody);

        // LTS API:创建日志流
        CreateLogStreamRequestBody createLogStreamRequestBody = new CreateLogStreamRequestBody();
        createLogStreamRequestBody.setLogStreamName(logName);
        String logStreamId =
            ltsApi.createLogStream(ltsEndpoint, headers, iam.getProjectId(), logGroupId, createLogStreamRequestBody);

        // 保存操作日志资源信息
        LogResourceOp logResourceOp = new LogResourceOp();
        logResourceOp.setRegion(iam.getRegion())
            .setProjectId(iam.getProjectId())
            .setLogStreamId(logStreamId)
            .setLogGroupId(logGroupId)
            .setLogStreamName(logName)
            .setLogGroupName(logName)
            .setEndpoint(ltsEndpoint)
            .setTenantCode(database);
        logResourceOpBaseMapper.insert(logResourceOp);

        log.info("========== end: 创建操作日志资源 ==========");
    }

    @Override
    public void tryInitSysLogResource() {
        log.info("========== start: 创建系统日志资源 ==========");

        // IAM API:构造带有 IAM Token 的请求头
        Map<String, String> headers = IamApi.getIamHeader(iam.getEndpoint(), iam.getIamDomain(), iam.getIamUser(),
            iam.getIamPassword(), iam.getProjectId());

        String logGroupName = database.concat(SYS_LOG_SUFFIX);
        // LTS API:创建日志组
        CreateLogGroupRequestBody createLogGroupRequestBody =
            new CreateLogGroupRequestBody().setLogGroupName(logGroupName).setTtlInDays(LOG_MAX_TTL);
        String logGroupId = ltsApi.createLogGroup(ltsEndpoint, headers, iam.getProjectId(), createLogGroupRequestBody);

        // 平台侧控制台应用组件信息
        Long appId = appTemplateBaseMapper.selectIdByType(1);
        List<AppComponent> componentList = componentInfoMapper.selectList(new QueryWrap<AppComponent>().lambda()
            .ne(AppComponent::getLabel, ComponentLabelEnum.B_UI.getLabel())
            .eq(AppComponent::getAppId, appId));

        // 系统日志资源信息
        LogResourceSys logResourceSys = new LogResourceSys();
        logResourceSys.setRegion(iam.getRegion())
            .setProjectId(iam.getProjectId())
            .setLogGroupName(logGroupName)
            .setTenantAppCode(database)
            .setLogGroupId(logGroupId)
            .setEndpoint(ltsEndpoint)
            .setTenantCode(database)
            .setAppId(appId);

        for (AppComponent component : componentList) {
            // LTS API:创建日志流
            CreateLogStreamRequestBody createLogStreamRequestBody = new CreateLogStreamRequestBody();
            createLogStreamRequestBody.setLogStreamName(component.getName());
            String logStreamId = ltsApi.createLogStream(ltsEndpoint, headers, iam.getProjectId(), logGroupId,
                createLogStreamRequestBody);

            logResourceSys.setComponentId(component.getId())
                .setLogStreamName(component.getName())
                .setLogStreamId(logStreamId)
                .setId(null);
            logResourceSysBaseMapper.insert(logResourceSys);
        }

        log.info("========== end: 创建系统日志资源 ==========");
    }

    @Override
    public void tryDeleteLogResource() {
        if (!rdsRepository.isTableExist(database, ResourceNamingUtil.getPrefixCode().concat(SYS_LOG_TABLE_NAME))) {
            log.info("数据库表未创建，跳过删除资源.");
            return;
        }
        log.info("========== start: 删除日志资源 ==========");

        log.info("开始删除操作日志资源");
        Map<String, String> headers = IamApi.getIamHeader(iam.getEndpoint(), iam.getIamDomain(), iam.getIamUser(),
            iam.getIamPassword(), iam.getProjectId());

        List<String> logGroupIdList = logResourceOpBaseMapper.getLogGroupId();
        for (String logGroupId : logGroupIdList) {
            try {
                // LTS API:删除日志组
                ltsApi.deleteLogGroup(ltsEndpoint, headers, iam.getProjectId(), logGroupId);
            } catch (Exception e) {
                log.error(e.toString());
            }
        }

        log.info("操作日志资源删除完毕...开始删除系统日志资源");

        List<String> sysLogGroupIdList = logResourceSysBaseMapper.getLogGroupId();
        for (String logGroupId : sysLogGroupIdList) {
            // LTS API:删除日志组
            try {
                ltsApi.deleteLogGroup(ltsEndpoint, headers, iam.getProjectId(), logGroupId);
            } catch (Exception e) {
                log.error(e.toString());
            }
        }

        log.info("系统日志资源删除完毕");

        log.info("========== end: 删除日志资源 ==========");
    }

    /**
     * 创建AOM日志接入规则
     */
    @Override
    public void initMappingRule() {
        log.info("开始创建AOM日志接入规则...");
        // 单region可以用selectOne，多region会报错
        CceInfo cceInfo = cceInfoBaseMapper.selectOne(new QueryWrap<CceInfo>().lambda().eq(CceInfo::getIsDel, false));
        List<LogResourceSys> sysList = logResourceSysBaseMapper
            .selectList(new QueryWrap<LogResourceSys>().lambda().eq(LogResourceSys::getIsDel, false));

        Map<String, String> headers = IamApi.getIamHeader(iam.getEndpoint(), iam.getIamDomain(), iam.getIamUser(),
            iam.getIamPassword(), iam.getProjectId());
        for (LogResourceSys sys : sysList) {
            List<CceDeploymentResource> resourceList =
                cceDeploymentResourceBaseMapper.selectList(new QueryWrap<CceDeploymentResource>().lambda()
                    // install时，平台侧的tenantAppCode就是CCE的命名空间
                    .eq(CceDeploymentResource::getComponentId, sys.getComponentId())
                    .eq(CceDeploymentResource::getNamespace, sys.getTenantAppCode())
                    .eq(CceDeploymentResource::getAppId, sys.getAppId()));
            if (resourceList.isEmpty()) {
                log.info("日志流<{}>没有对应的工作负载，跳过创建AOM日志接入规则...", sys.getLogStreamName());
                continue;
            }

            LogStreamInfo logStreamInfo = new LogStreamInfo(sys.getLogGroupId(), sys.getLogGroupName(),
                sys.getLogStreamId(), sys.getLogStreamName());

            RuleInfo ruleInfo = new RuleInfo()
                .setFiles(ListUtil
                    .toList(new LogFile().setFileName(LtsConstant.ALL_LOG_FILE).setLogStreamInfo(logStreamInfo)))
                .setDeployments(resourceList.stream().map(CceDeploymentResource::getDeployment).toList())
                .setClusterName(cceInfo.getClusterName())
                .setClusterId(cceInfo.getClusterId())
                .setNamespace(sys.getTenantAppCode());

            CreateMappingRuleRequestBody requestBody = new CreateMappingRuleRequestBody()
                .setRuleName(sys.getLogGroupName().concat("-").concat(sys.getLogStreamName()))
                .setProjectId(sys.getProjectId())
                .setRuleInfo(ruleInfo);

            AomMappingRuleResp response =
                ltsApi.createMappingRule(sys.getEndpoint(), headers, sys.getProjectId(), requestBody, false);
            sys.setMappingRuleId(response.getRuleId());
        }

        logResourceSysBaseMapper.batchUpdateMappingRuleId(sysList);
        log.info("创建AOM日志接入规则成功.");
    }

    /**
     * 删除AOM日志接入规则
     */
    @Override
    public void deleteMappingRule() {
        if (!rdsRepository.isTableExist(database, ResourceNamingUtil.getPrefixCode().concat(SYS_LOG_TABLE_NAME))) {
            log.info("数据库表未创建，跳过删除资源.");
            return;
        }

        log.info("开始删除AOM日志接入规则...");
        List<LogResourceSys> sysList = logResourceSysBaseMapper
            .selectList(new QueryWrap<LogResourceSys>().lambda().eq(LogResourceSys::getIsDel, false));

        Map<String, String> headers = IamApi.getIamHeader(iam.getEndpoint(), iam.getIamDomain(), iam.getIamUser(),
            iam.getIamPassword(), iam.getProjectId());
        // 共享应用的系统日志接入规则ID相同，但只能删除一次，否则会报错
        Set<String> ruleIdSet = new HashSet<>();
        for (LogResourceSys sys : sysList) {
            if (StringUtils.isNotBlank(sys.getMappingRuleId()) && !ruleIdSet.contains(sys.getMappingRuleId())) {
                ltsApi.deleteMappingRule(sys.getEndpoint(), headers, sys.getProjectId(), sys.getMappingRuleId());
                ruleIdSet.add(sys.getMappingRuleId());
            }
        }

        log.info("删除AOM日志接入规则成功.");
    }
}
